<!DOCTYPE html>
<html lang="zh-CN">
  <head>
  <meta charset="UTF-8">
  <meta 
    name="viewport"
    content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
  <meta 
    http-equiv="X-UA-Compatible" 
    content="ie=edge">
  <meta 
    name="theme-color" 
    content="#fff" 
    id="theme-color">
  <meta 
    name="description" 
    content="Gas">
  <link 
    rel="icon" 
    href="/img/Kaze.png">
  <title>Java-NIO</title>
  
    
      <meta 
        property="og:title" 
        content="Java-NIO">
    
    
      <meta 
        property="og:url" 
        content="http://example.com/2021/09/28/Java-NIO/index.html">
    
    
      <meta 
        property="og:img" 
        content="asdfghjkl.jpg">
    
    
    
      <meta 
        property="og:type" 
        content="article">
      <meta 
        property="og:article:published_time" 
        content="2021-09-28">
      <meta 
        property="og:article:modified_time" 
        content="2021-12-04">
      <meta 
        property="og:article:author" 
        content="Gas">
      
        
          <meta 
            property="og:article:tag" 
            content="Java">
        
          <meta 
            property="og:article:tag" 
            content="NIO">
        
      
    
  
  <script>
    function loadScript(url, cb) {
      var script = document.createElement('script');
      script.src = url;
      if (cb) script.onload = cb;
      script.async = true;
      document.body.appendChild(script);
    }
    function loadCSS(href, data, attr) {
      var sheet = document.createElement('link');
      sheet.ref = 'stylesheet';
      sheet.href = href;
      sheet.dataset[data] = attr;
      document.head.appendChild(sheet);
    }
    function changeCSS(cssFile, data, attr) {
      var oldlink = document.querySelector(data);
      var newlink = document.createElement("link");
      newlink.setAttribute("rel", "stylesheet");
      newlink.setAttribute("href", cssFile);
      newlink.dataset.prism = attr;
      document.head.replaceChild(newlink, oldlink);
    }
  </script>
  
    
      
      
      
      
        
        
        
        <script>
          function prismThemeChange() {
            if(document.getElementById('theme-color').dataset.mode === 'dark') {
              if(document.querySelector('[data-prism]')) {
                changeCSS('/js/lib/prism/prism-tomorrow.min.css', '[data-prism]', 'prism-tomorrow');
              } else {
                loadCSS('/js/lib/prism/prism-tomorrow.min.css', 'prism', 'prism-tomorrow');
              }
            } else {
              if(document.querySelector('[data-prism]')) {
                changeCSS('/js/lib/prism/prism-okaidia.min.css', '[data-prism]', 'prism-okaidia');
              } else {
                loadCSS('/js/lib/prism/prism-okaidia.min.css', 'prism', 'prism-okaidia');
              }
            }
          }
          prismThemeChange()
        </script>
      
      
        
        <link rel="stylesheet" href="/js/lib/prism/prism-line-numbers.min.css">
      
    
  
  <script>
    // control reverse button
    var reverseDarkList = {
      dark: 'light',
      light: 'dark'
    };
    var themeColor = {
      dark: '#1c1c1e',
      light: '#fff'
    }
    // get the data of css prefers-color-scheme
    var getCssMediaQuery = function() {
      return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    };
    // reverse current darkmode setting function
    var reverseDarkModeSetting = function() {
      var setting = localStorage.getItem('user-color-scheme');
      if(reverseDarkList[setting]) {
        setting = reverseDarkList[setting];
      } else if(setting === null) {
        setting = reverseDarkList[getCssMediaQuery()];
      } else {
        return;
      }
      localStorage.setItem('user-color-scheme', setting);
      return setting;
    };
    // apply current darkmode setting
  </script>
  
    <script>
      var setDarkmode = function(mode) {
      var setting = mode || localStorage.getItem('user-color-scheme');
      if(setting === getCssMediaQuery()) {
        document.documentElement.removeAttribute('data-user-color-scheme');
        localStorage.removeItem('user-color-scheme');
        document.getElementById('theme-color').content = themeColor[setting];
        document.getElementById('theme-color').dataset.mode = setting;
        prismThemeChange();
      } else if(reverseDarkList[setting]) {
        document.documentElement.setAttribute('data-user-color-scheme', setting);
        document.getElementById('theme-color').content = themeColor[setting];
        document.getElementById('theme-color').dataset.mode = setting;
        prismThemeChange();
      } else {
        document.documentElement.removeAttribute('data-user-color-scheme');
        localStorage.removeItem('user-color-scheme');
        document.getElementById('theme-color').content = themeColor[getCssMediaQuery()];
        document.getElementById('theme-color').dataset.mode = getCssMediaQuery();
        prismThemeChange();
      }
    };
    setDarkmode();
    </script>
  
  
  <link rel="preload" href="//at.alicdn.com/t/font_1946621_i1kgafibvw.css" as="style" >
  <link rel="preload" href="//at.alicdn.com/t/font_1952792_89b4ac4k4up.css" as="style" >
  
  
    <link rel="preload" href="/js/lib/lightbox/baguetteBox.min.js" as="script">
    <link rel="preload" href="/js/lib/lightbox/baguetteBox.min.css" as="style" >
  
  
    <link rel="preload" href="/js/lib/lozad.min.js" as="script">
  
  
  
  
  
  
  
  <link rel="stylesheet" href="/css/main.css">
  
  <link rel="stylesheet" href="//at.alicdn.com/t/font_1946621_i1kgafibvw.css">
  
  <link rel="stylesheet" href="//at.alicdn.com/t/font_1952792_89b4ac4k4up.css">
  <link rel="stylesheet" href="//at.alicdn.com/t/font_2818401_duebq0kmk47.css">
  
    <link rel="stylesheet" href="/js/lib/lightbox/baguetteBox.min.css">
  
<meta name="generator" content="Hexo 5.4.0"></head>

  <body>
    <div class="wrapper">
       
      <nav class="navbar">
  <div class="navbar-logo">
    <span class="navbar-logo-main">
      
        <img 
          class="navbar-logo-img" 
          src="/img/Kaze.png" 
          alt="blog logo">
      
      <span class="navbar-logo-dsc">Gas</span>
    </span>
  </div>
  <div class="navbar-menu">
    
      <a 
        href="/" 
        class="navbar-menu-item">
        
          首页
        
      </a>
    
      <a 
        href="/archives" 
        class="navbar-menu-item">
        
          归档
        
      </a>
    
      <a 
        href="/tags" 
        class="navbar-menu-item">
        
          标签
        
      </a>
    
      <a 
        href="/categories" 
        class="navbar-menu-item">
        
          分类
        
      </a>
    
      <a 
        href="/about" 
        class="navbar-menu-item">
        
          关于
        
      </a>
    
      <a 
        href="/links" 
        class="navbar-menu-item">
        
          友链
        
      </a>
    
    <a 
      class="navbar-menu-item darknavbar" 
      id="dark">
      <i class="iconfont icon-weather"></i>
    </a>
    <a 
      class="navbar-menu-item searchnavbar" 
      id="search">
      <i 
        class="iconfont icon-search" 
        style="font-size: 1.2rem; font-weight: 400;">
      </i>
    </a>
  </div>
</nav> 
      
      <div 
        id="local-search" 
        style="display: none">
        <input
          class="navbar-menu-item"
          id="search-input"
          placeholder="请输入搜索内容..." />
        <div id="search-content"></div>
      </div>
      
      <div class="section-wrap">
        <div class="container">
          <div class="columns">
            <main class="main-column">
  <div class="image-wrapper">
    <img 
      src="asdfghjkl.jpg" 
      data-src="asdfghjkl.jpg"
      srcset="data:image/svg+xml,%3Csvg%20xmlns=&#39;http://www.w3.org/2000/svg&#39;%20viewBox=&#39;0%200%20300%20300&#39;%3E%3C/svg%3E"
      class="image lozad"
      alt="Java-NIO thumbnail">
  </div>

<article class="card card-content">
  <header>
    <h1 class="post-title">
      Java-NIO
    </h1>
  </header>
  <div class="post-meta post-show-meta">
    <time datetime="2021-09-28T07:29:15.000Z">
      <i 
        class="iconfont icon-calendar" 
        style="margin-right: 2px;">
      </i>
      <span>2021-09-28</span>
    </time>
    
      <span class="dot"></span>
      
        <a 
          href="/categories/Java/" 
          class="post-meta-link">
          Java
        </a>
      
    
    
      <span class="dot"></span>
      <span>7k 字</span>
    
  </div>
  
    <div 
      class="post-meta post-show-meta" 
      style="margin-top: -10px;">
      <div style="display: flex; align-items: center;">
        <i 
          class="iconfont icon-biaoqian" 
          style="margin-right: 2px; font-size: 1.15rem;">
        </i>
        
          
          <a 
            href="/tags/Java/" 
            class="post-meta-link">
            Java
          </a>
        
          
            <span class="dot"></span>
          
          <a 
            href="/tags/NIO/" 
            class="post-meta-link">
            NIO
          </a>
        
      </div>
    </div>
  
  </header>
  <div 
    id="section" 
    class="post-content">
    <h2 id="Java-NIO-概述"><a href="#Java-NIO-概述" class="headerlink" title="Java NIO 概述"></a>Java NIO 概述</h2><p>NIO主要有三大核心部分: <code>Channel(通道)</code>、<code>Buffer(缓冲区)</code>、<code>Selector</code>(选择区),区别与传统<code>IO</code>基于字节流和字符流进行操作,而<code>NIO</code>基于<code>Channel</code>和<code>Buffer</code>进行操作,数据总是从通道读取到缓存区中,或者从缓冲区写入到通道中,<code>Selector</code>用与监听多个通道的事件.因此单线程可以监听多个数据通道.</p>
<h2 id="Channel-和-Buffer"><a href="#Channel-和-Buffer" class="headerlink" title="Channel 和 Buffer"></a>Channel 和 Buffer</h2><p>基本上，所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中，也可以从Buffer 写到Channel中。</p>
<p><img src="/2021/09/28/Java-NIO/image-20211128222910933.png" alt="image-20211128222910933" srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="/2021/09/28/Java-NIO/image-20211128222910933.png" class="lozad post-image"></p>
<p>Channel的主要实现:</p>
<ul>
<li>FileChannel</li>
<li>DatagramChannel</li>
<li>SocketChannel</li>
<li>ServerSocketChannel</li>
</ul>
<p>这些实现涵盖了UDP和TCP及网络IO与文件IO.</p>
<hr>
<p>Buffer的主要实现hexo</p>
<ul>
<li>ByteBuffer</li>
<li>CharBuffer</li>
<li>DoubleBuffer</li>
<li>FloatBuffer</li>
<li>IntBuffer</li>
<li>LongBuffer</li>
<li>ShortBuffer</li>
<li>MappedByteBuffer(用于内存映射)</li>
</ul>
<p>这些Buffer覆盖了你能通过IO发送的基本数据类型：byte, short, int, long, float, double 和 char。</p>
<h2 id="Selector"><a href="#Selector" class="headerlink" title="Selector"></a>Selector</h2><p>Selector允许单个线程处理多个Channel,如果你的应用打开了多个连接(通道),但每个流量都很低(低流量场景),使用Selector会方便.</p>
<p><img src="/2021/09/28/Java-NIO/image-20211128223911936.png" alt="image-20211128223911936" srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="/2021/09/28/Java-NIO/image-20211128223911936.png" class="lozad post-image"></p>
<p>要使用Selector,得向Selector注册Channel,然后调用它的<code>Select()</code>方法.<strong>这个方法会一直阻塞到某个注册的通道有事件就绪</strong>,一旦这个方法返回,线程就可以处理这些事件.</p>
<h2 id="Buffer"><a href="#Buffer" class="headerlink" title="Buffer"></a>Buffer</h2><p>使用Buffer读写数据一般遵循四个步骤</p>
<ol>
<li>写入数据到<code>Buffer</code></li>
<li>调用<code>flip()</code>方法</li>
<li>从<code>Buffer</code>中读取数据</li>
<li>调用<code>clear()</code>方法或者<code>compact()</code>方法</li>
</ol>
<p>向buffer写入数据时,buffer会记录写入了多少数据,一旦要读取数据,需要通过<code>filp()</code>方法将buffer切换到读模式,在读模式下,可以读取之前写入到buffer的所有数据.</p>
<p>一旦读完了所有数据,就需要清空缓冲区,让它可以被再次写入,有两种方法能清空缓冲区:</p>
<ol>
<li><code>claer()</code>清空整个缓冲区</li>
<li><code>compact()</code>清除已读过的数据,未读的会被移动至缓冲区起始处,新数据接在后面</li>
</ol>
<p>案例:</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">File</span> file <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">File</span><span class="token punctuation">(</span><span class="token string">"test.txt"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">FileChannel</span> fc <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RandomAccessFile</span><span class="token punctuation">(</span>file<span class="token punctuation">,</span> <span class="token string">"rw"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">//创建容量为 3 字节的缓冲区</span>
<span class="token class-name">ByteBuffer</span> buf <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">int</span> bytesRead<span class="token punctuation">;</span>
<span class="token comment">//读入缓冲区</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>bytesRead <span class="token operator">=</span> fc<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>buf<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Read-"</span> <span class="token operator">+</span> bytesRead<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">//使Buffer状态变为准备读</span>
    buf<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">while</span> <span class="token punctuation">(</span>buf<span class="token punctuation">.</span><span class="token function">hasRemaining</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
        <span class="token comment">//一次读取 1 个字节</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">char</span><span class="token punctuation">)</span> buf<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
    
    <span class="token comment">//清空缓冲区,准备重新写入</span>
    buf<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<hr>
<p>Buffer(缓冲区)的本质是一块可以写入数据,然后可以从中读取数据的内存,这块内存被包装为NIO Buffer对象,提供了一组方法,用来方便访问该块内存.</p>
<p>为了理解Buffer的工作原理,需要熟悉它的三个属性:</p>
<ul>
<li>capacity</li>
<li>position</li>
<li>limit</li>
</ul>
<p>position和limit的含义取决于Buffer是处于<strong>读模式</strong>还是<strong>写模式</strong>.不管Buffer处于什么模式,capacity的含义是一样的.</p>
<p><img src="/2021/09/28/Java-NIO/image-20211128224235167.png" alt="image-20211128224235167" srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="/2021/09/28/Java-NIO/image-20211128224235167.png" class="lozad post-image"></p>
<blockquote>
<p><strong>capacity</strong></p>
<p>作为一个内存块,Buffer有一个固定的大小,值为capacity,你只能往里面写入capacity个byte、long、char等类型,一旦写满,需要将其清空（通过读数据或者清除数据）才能继续写入数据.</p>
</blockquote>
<blockquote>
<p><strong>position</strong></p>
<p>写模式：position表示当前位置,初始的position为0,当一个byte、long等数据写入Buffer后,position会向前移动到下一个可插入数据的Buffer单元,position最大可为 capacity-1    </p>
<p>读模式：从某个特定的位置读,将当前Buffer从<strong>写模式</strong>切换到<strong>读模式</strong>,position会被重置为0,当从Buffer的position处读取数据时,position向前移动到下一个可读位置.</p>
</blockquote>
<blockquote>
<p><strong>limit</strong></p>
<p>写模式：Buffer的limit表示你最多能往Buffer里写入多少数据.写模式下,limit最大值等于capacity.</p>
<p>读模式：limit表示最多能读多少数据,因此,当切换Buffer到<strong>读模式</strong>时,limit会被设置为写模式下的position的值.换句话说,你能读到你之前写入的所有数据.(limit被设置成已写数据的数量，这个值在写模式下就是position)</p>
</blockquote>
<h3 id="Buffer的类型"><a href="#Buffer的类型" class="headerlink" title="Buffer的类型"></a>Buffer的类型</h3><p>Java NIO有一下Buffer类型</p>
<ul>
<li>ByteBuffer</li>
<li>MappedByteBuffer</li>
<li>CharBuffer</li>
<li>DoubleBuffer</li>
<li>FloatBuffer</li>
<li>IntBuffer</li>
<li>LongBuffer</li>
<li>ShortBuffer</li>
</ul>
<p>不太Buffer对应不同数据类型,换句话说,就是可以通过char、short、int、long、float或double类型来操作缓冲区中的字节.</p>
<h3 id="Buffer的分配"><a href="#Buffer的分配" class="headerlink" title="Buffer的分配"></a>Buffer的分配</h3><p>想要获得一个Buffer对象首先要进行分配,每个Buffer类都有一个allocate方法,下面是一个分配48字节<code>capacity</code>的ByteBuffer的例子.</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">ByteBuffer</span> buf <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">48</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocateDirect</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">/**
  * class java.nio.HeapByteBuffer    -使用Java堆内存，读写效率低，受到GC影响
  * class java.nio.DirectByteBuffer  -使用直接内存。读写效率高（少一次数据拷贝），不会受到GC影响,分配的效率低，使用不当会内存泄露
*/</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="Buffer写入数据"><a href="#Buffer写入数据" class="headerlink" title="Buffer写入数据"></a>Buffer写入数据</h3><p>写入数据到Buffer有两种方法:</p>
<ol>
<li><p>从<code>Channel</code>写入到<code>Buffer</code></p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">int</span> bytesRead <span class="token operator">=</span> inChannel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>buf<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//read into buffer.</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li>
<li><p>通过<code>Buffer</code>的<code>put()</code>方法写到Buffer里</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java">buf<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span><span class="token number">127</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li>
</ol>
<p><code>put()</code>方法有很多版本,允许你以不同方法从<code>Buffer</code>中读取数据,例如,从指定<code>position</code>读取,或者从<code>Buffer</code>中读取数据到字节数组,更多Buffer实现的细节参考JavaDoc.</p>
<hr>
<h3 id="Buffer读取数据"><a href="#Buffer读取数据" class="headerlink" title="Buffer读取数据"></a>Buffer读取数据</h3><p>读取数据到Buffer有两种方法:</p>
<ol>
<li><p>调用<code>channel</code>的<code>write</code>方法</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">int</span> writeBytes <span class="token operator">=</span> channel<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>buf<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li>
<li><p>调用<code>buffer</code>自己的<code>get</code>方法</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">byte</span> b <span class="token operator">=</span> buf<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li>
</ol>
<p><code>get ()</code>方法会让<code>position</code>读指针向后走，如果想重复读取数据,可以调用 <code>rewind </code>方法将 <code>position </code>重新置为 0,或者调用 <code>get(int i)</code> 方法获取索引 i 的内容，它不会移动读指针.</p>
<hr>
<p><strong><code>rewind()</code>方法</strong></p>
<p><code>Buffer.rewind()</code>将<code>position</code>设回0,所以可以重读<code>Buffer</code>中的内容,<code>limit</code>保持不变.</p>
<p><strong><code>clear()</code>和<code>compact()</code>方法</strong></p>
<p>一旦读完<code>Buffer</code>中的数据,需要清空<code>Buffer</code>以备再次写入,可以通过<code>clear()</code>或<code>compact()</code>方法来完成.</p>
<p>调用的是<code>clear()</code>方法,<code>position</code>将被设置为0,<code>limit</code>被设置为<code>capacity</code>值,意思是,<code>Buffer</code>被清空,但是数据未清除,只是标记我们可以从那个标志位开始向<code>Buffer</code>写入数据.</p>
<p>如果<code>Buffer</code>中有一些未读数据,调用<code>clear()</code>方法,数据将会被遗忘,意味着不会标记任何数据是读过的还是未读的.</p>
<p>如果希望保留未读的数据,且后续需要这些数据,但又要先写入一些数据,则需使用<code>compact()</code>方法</p>
<p><code>compact()</code>方法将未读的数据拷贝到<code>Buffer</code>起始处,然后将<code>position</code>设到最后一个未读元素后面,<code>limit</code>属性依然和<code>clear()</code>一样,设置为<code>capacity</code>,现在<code>Buffer</code>准备好写入数据了,但不会覆盖未读数据.</p>
<p><strong><code>mark()</code>与<code>reset()</code>方法</strong></p>
<p>调用<code>Buffer.make()</code>方法,可以标记<code>Buffer</code>中的一个特定<code>position</code>,之后可以同过<code>Buffer.reset()</code>方法将<code>position</code>恢复到<code>make</code>位置.</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java">buffer<span class="token punctuation">.</span><span class="token function">mark</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 调用 buffer.get() 几次，例如 在解析过程中。</span>
buffer<span class="token punctuation">.</span><span class="token function">reset</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//将位置设置回标记</span>
<span class="token operator">/</span>注意
rewind 和 flip 都会清除 mark 位置<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><strong><code>equals()</code>方法</strong></p>
<p>满足以下条件时为<code>true</code></p>
<ol>
<li>相同的类型</li>
<li><code>Buffer</code>中剩余的byte、char等的个数相等</li>
<li>Buffer中所有剩余的byte、char等都相同</li>
</ol>
<p><code>equals</code>只是比较<code>Buffer</code>的一部分,不是每一个在它里面的元素都比较.实际上,它只比较Buffer中的剩余元素.</p>
<p><strong><code>comareTo()</code>方法</strong></p>
<p>compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer：</p>
<ol>
<li>第一个不相等的元素小于另一个Buffer中对应的元素 。</li>
<li>所有元素都相等，但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。</li>
</ol>
<p><strong>Buffer与字符串的转换</strong></p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">//1. 字符串转为ByteBuffer</span>
<span class="token class-name">ByteBuffer</span> buffer1 <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
buffer1<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">ByteBufferUtil</span><span class="token punctuation">.</span><span class="token function">debugAll</span><span class="token punctuation">(</span>buffer1<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">//2. Charset</span>
<span class="token class-name">ByteBuffer</span> buffer2 <span class="token operator">=</span> <span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//会自动切换到读模式</span>
<span class="token class-name">ByteBufferUtil</span><span class="token punctuation">.</span><span class="token function">debugAll</span><span class="token punctuation">(</span>buffer2<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">//3. wrap</span>
<span class="token class-name">ByteBuffer</span> buffer3 <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">wrap</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//会自动切换到读模式</span>
<span class="token class-name">ByteBufferUtil</span><span class="token punctuation">.</span><span class="token function">debugAll</span><span class="token punctuation">(</span>buffer3<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">/**--*--buffer转字符串--*--**/</span>

<span class="token class-name">CharBuffer</span> str1 <span class="token operator">=</span> <span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">.</span><span class="token function">decode</span><span class="token punctuation">(</span>buffer2<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>str1<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">//需要先切换到读模式</span>
buffer1<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">CharBuffer</span> str2 <span class="token operator">=</span> <span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">.</span><span class="token function">decode</span><span class="token punctuation">(</span>buffer1<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>str2<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="Scatter-Gather"><a href="#Scatter-Gather" class="headerlink" title="Scatter/Gather"></a>Scatter/Gather</h2><p>Scatter(分散)从Channel中读取是指在<strong>读操作</strong>时将读取的数据写入多个Buffer中。因此，Channel将从Channel中读取的数据“分散(Scatter)”到多个Buffer中。</p>
<p>Gather(集散)写入Channel是指在<strong>写操作</strong>时将多个Buffer的数据写入同一个Channel中，因此，Channel将多个Buffer中的数据“聚集(Gather)”后发送到Buffer中。</p>
<p>scatter / gather经常用于需要将传输的数据分开处理的场合，例如传输一个由消息头和消息体组成的消息，你可能会将消息体和消息头分散到不同的buffer中，这样你可以方便的处理消息头和消息体。</p>
<h3 id="Scatterering-Reads"><a href="#Scatterering-Reads" class="headerlink" title="Scatterering Reads"></a>Scatterering Reads</h3><p>Scattering Reads是指数据从一个channel读取到多个buffer中。如下图描述：</p>
<p><img src="/2021/09/28/Java-NIO/image-20211004161855199.png" alt="image-20211004161855199" srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="/2021/09/28/Java-NIO/image-20211004161855199.png" class="lozad post-image"></p>
<p>代码示例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">FileChannel</span> channel <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RandomAccessFile</span><span class="token punctuation">(</span><span class="token string">"words.txt"</span><span class="token punctuation">,</span> <span class="token string">"r"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token class-name">ByteBuffer</span> b1 <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">ByteBuffer</span> b2 <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">ByteBuffer</span> b3 <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    channel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">&#123;</span>b1<span class="token punctuation">,</span> b2<span class="token punctuation">,</span> b3<span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    b1<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    b2<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    b3<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token class-name">ByteBufferUtil</span><span class="token punctuation">.</span><span class="token function">debugAll</span><span class="token punctuation">(</span>b1<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">ByteBufferUtil</span><span class="token punctuation">.</span><span class="token function">debugAll</span><span class="token punctuation">(</span>b2<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">ByteBufferUtil</span><span class="token punctuation">.</span><span class="token function">debugAll</span><span class="token punctuation">(</span>b3<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
<span class="token punctuation">&#125;</span> <span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>这里需要注意，Buffer数组作为<code>read()</code>的入参，会按顺序将将数据写入到Buffer中，当写满一个才会到下一个。</p>
<p>Scattering Reads在移动下一个buffer前，必须填满当前的buffer，意味着它不适合动态的消息，或者说大小不固定的消息，换句话说，如果存在消息头和消息体，消息头必须完成填充（例如 128byte），Scattering Reads才能正常工作。</p>
<h3 id="Gathering-Writes"><a href="#Gathering-Writes" class="headerlink" title="Gathering Writes"></a>Gathering Writes</h3><p>Gathering Writes是指数据从多个buffer写入到同一个channel。如下图描述：</p>
<p><img src="/2021/09/28/Java-NIO/image-20211004162805846.png" alt="image-20211004162805846" srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="/2021/09/28/Java-NIO/image-20211004162805846.png" class="lozad post-image"></p>
<p>代码示例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">ByteBuffer</span> b1 <span class="token operator">=</span> <span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">ByteBuffer</span> b2 <span class="token operator">=</span> <span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span><span class="token string">"world"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">ByteBuffer</span> b3 <span class="token operator">=</span> <span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span>UTF_8<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span><span class="token string">"你好"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">FileChannel</span> channel <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RandomAccessFile</span><span class="token punctuation">(</span><span class="token string">"words2.txt"</span><span class="token punctuation">,</span> <span class="token string">"rw"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    channel<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">&#123;</span>b1<span class="token punctuation">,</span> b2<span class="token punctuation">,</span> b3<span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>buffers数组是write()方法的入参，write()方法会按照buffer在数组中的顺序，将数据写入到channel，注意只有position和limit之间的数据才会被写入。因此，如果一个buffer的容量为128byte，但是仅仅包含58byte的数据，那么这58byte的数据将被写入到channel中。因此与Scattering Reads相反，Gathering Writes能较好的处理动态消息。</p>
<h2 id="通道之间的数据传输"><a href="#通道之间的数据传输" class="headerlink" title="通道之间的数据传输"></a>通道之间的数据传输</h2><p>在NIO中，如果两个通道中有一个是<code>FileChannel</code>，那就可以之间将数据从一个<code>Channel</code>拷贝到另一个<code>Channel</code>。涉及到<strong>零拷贝</strong>，后面有写</p>
<h3 id="transferTo"><a href="#transferTo" class="headerlink" title="transferTo()"></a>transferTo()</h3><p>transferTo()方法将数据从FileChannel传输到其他的channel中。下面是一个简单的例子：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">try</span> <span class="token punctuation">&#123;</span>
    <span class="token class-name">FileChannel</span> from <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span><span class="token string">"data.txt"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">FileChannel</span> <span class="token keyword">to</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileOutputStream</span><span class="token punctuation">(</span><span class="token string">"to.txt"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 效率高 底层会利用操作系统的零拷贝优化 注意：一次最大传2G 下面解决方法</span>
    from<span class="token punctuation">.</span><span class="token function">transferTo</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> from<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">to</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span>

<span class="token comment">//解决方法</span>
<span class="token keyword">try</span> <span class="token punctuation">&#123;</span>
    <span class="token class-name">FileChannel</span> from <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span><span class="token string">"data.txt"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">FileChannel</span> <span class="token keyword">to</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileOutputStream</span><span class="token punctuation">(</span><span class="token string">"to.txt"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 效率高 底层会利用操作系统的零拷贝优化 一次最大传2G</span>
    <span class="token keyword">long</span> size <span class="token operator">=</span> from<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//left代表还剩余多少数据没有传输</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">long</span> left <span class="token operator">=</span> size<span class="token punctuation">;</span> left <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"position: "</span> <span class="token operator">+</span> <span class="token punctuation">(</span>size <span class="token operator">-</span> left<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" left: "</span> <span class="token operator">+</span> left<span class="token punctuation">)</span><span class="token punctuation">;</span>
        left <span class="token operator">-=</span> from<span class="token punctuation">.</span><span class="token function">transferTo</span><span class="token punctuation">(</span>size <span class="token operator">-</span> left<span class="token punctuation">,</span> left<span class="token punctuation">,</span> <span class="token keyword">to</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="transferFrom"><a href="#transferFrom" class="headerlink" title="transferFrom()"></a>transferFrom()</h3><p>FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中。</p>
<p>简单例子：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">RandomAccessFile</span> fromFile <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RandomAccessFile</span><span class="token punctuation">(</span><span class="token string">"fromFile.txt"</span><span class="token punctuation">,</span> <span class="token string">"rw"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">FileChannel</span>      fromChannel <span class="token operator">=</span> fromFile<span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">RandomAccessFile</span> toFile <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RandomAccessFile</span><span class="token punctuation">(</span><span class="token string">"toFile.txt"</span><span class="token punctuation">,</span> <span class="token string">"rw"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">FileChannel</span>      toChannel <span class="token operator">=</span> toFile<span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">long</span> position <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token keyword">long</span> count <span class="token operator">=</span> fromChannel<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
toChannel<span class="token punctuation">.</span><span class="token function">transferFrom</span><span class="token punctuation">(</span>position<span class="token punctuation">,</span> count<span class="token punctuation">,</span> fromChannel<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>方法的输入参数position表示从position处开始向目标文件写入数据，count表示最多传输的字节数。如果源通道的剩余空间小于 count 个字节，则所传输的字节数要小于请求的字节数。</p>
<p>此外要注意，在SoketChannel的实现中，SocketChannel只会传输此刻准备好的数据（可能不足count字节）。因此，SocketChannel可能不会将请求的所有数据(count个字节)全部传输到FileChannel中。</p>
<p>是不是发现这个例子和前面那个例子特别相似？除了调用方法的FileChannel对象不一样外，其他的都一样。</p>
<h2 id="Selector-1"><a href="#Selector-1" class="headerlink" title="Selector"></a>Selector</h2><p>Selector(选择器)是Java NIO中能够检测一到多个NIO通道，并能够知晓通道是否为诸如读写事件做好准备的组件，这样，一个单独的线程可以管理多个Channel，从而管理多个网络连接。</p>
<h3 id="为什么使用Selector"><a href="#为什么使用Selector" class="headerlink" title="为什么使用Selector"></a>为什么使用Selector</h3><p>仅用单个线程来处理多个Channels的好处是，只需要更少的线程来处理通道。事实上，可以只用一个线程处理所有的通道。对于操作系统来说，线程之间上下文切换的开销很大，而且每个线程都要占用系统的一些资源（如内存）。因此，使用的线程越少越好。</p>
<p>但是，需要记住，现代的操作系统和CPU在多任务方面表现的越来越好，所以多线程的开销随着时间的推移，变得越来越小了。实际上，如果一个CPU有多个内核，不使用多任务可能是在浪费CPU能力。不管怎么说，关于那种设计的讨论应该放在另一篇不同的文章中。在这里，只要知道使用Selector能够处理多个通道就足够了。</p>
<p><img src="/2021/09/28/Java-NIO/image-20211005224348776.png" alt="image-20211005224348776" srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="/2021/09/28/Java-NIO/image-20211005224348776.png" class="lozad post-image"></p>
<h3 id="Selector的创建"><a href="#Selector的创建" class="headerlink" title="Selector的创建"></a>Selector的创建</h3><p>通过调用<code>Selector.open()</code>方法创建一个Selector，如下：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Selector</span> selector <span class="token operator">=</span> <span class="token class-name">Selector</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h3 id="绑定Channel事件"><a href="#绑定Channel事件" class="headerlink" title="绑定Channel事件"></a>绑定Channel事件</h3><p>也称之为注册事件，为了Channel和Selector配合使用，必须将Channel注册到Selector上，通过<code>SelectableChannel.register()</code>方法实现，如下：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java">channel<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//设置非阻塞模式</span>
<span class="token comment">//向Selector注册Channel时，`Channel.register()`方法会返回一个SelectionKey对象，这个对象代表注册到该Selector的通道。</span>
<span class="token class-name">SelectionKey</span> key <span class="token operator">=</span> channel<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span> 绑定事件<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<ul>
<li>channel必须工作在非阻塞模式下</li>
<li>FileChannel 没用非阻塞模式，因此不能配合 selector 一起使用</li>
</ul>
<p>注意register()方法的第二个参数。这是一个“interest集合”，意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件：</p>
<ol>
<li>Connect - 客户端连接建立后触发</li>
<li>Accept - 会在有连接请求时触发</li>
<li>Read - 可读事件</li>
<li>Write - 可写事件</li>
</ol>
<p>通道触发了一个事件意思是该事件已经就绪。所以，某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。</p>
<p>这四种事件用SelectionKey的四个常量来表示：</p>
<ol>
<li>SelectionKey.OP_CONNECT</li>
<li>SelectionKey.OP_ACCEPT</li>
<li>SelectionKey.OP_READ</li>
<li>SelectionKey.OP_WRITE</li>
</ol>
<p>如果你对不止一种事件感兴趣，那么可以用“位或”操作符将常量连接起来，如下：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">int</span> interestSet <span class="token operator">=</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_READ <span class="token operator">|</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_WRITE<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>在下面还会继续提到interest集合。</p>
<h3 id="SelectionKey"><a href="#SelectionKey" class="headerlink" title="SelectionKey"></a>SelectionKey</h3><p>向Selector注册Channel时，<code>register()</code>方法会返回一个SelectionKey对象，这个对象包含了一些属性：</p>
<ul>
<li>interest集合</li>
<li>ready集合</li>
<li>Channel</li>
<li>Selector</li>
<li>附加对象(可选)</li>
</ul>
<blockquote>
<p><strong>interest集合</strong></p>
<p>就像<a target="_blank" rel="noopener" href="http://ifeve.com/selectors/#Registering">向Selector注册通道</a>一节中所描述的，interest集合是你所选择的感兴趣的事件集合。可以通过SelectionKey读写interest集合，像这样：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">int</span> interestSet <span class="token operator">=</span> selectionKey<span class="token punctuation">.</span><span class="token function">interestOps</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">boolean</span> isInterestedInAccept  <span class="token operator">=</span> <span class="token punctuation">(</span>interestSet <span class="token operator">&amp;</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_ACCEPT<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_ACCEPT；
<span class="token keyword">boolean</span> isInterestedInConnect <span class="token operator">=</span> interestSet <span class="token operator">&amp;</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_CONNECT<span class="token punctuation">;</span>
<span class="token keyword">boolean</span> isInterestedInRead    <span class="token operator">=</span> interestSet <span class="token operator">&amp;</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_READ<span class="token punctuation">;</span>
<span class="token keyword">boolean</span> isInterestedInWrite   <span class="token operator">=</span> interestSet <span class="token operator">&amp;</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_WRITE<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>可以看到，用“位与”操作insterest集合和SelectionKey常量，可以确定某个确定的事件是否在insterest集合中。</p>
</blockquote>
<blockquote>
<p><strong>ready集合</strong></p>
<p>ready集合是通道已经准备就绪的操作的集合，在一次选择(Selection)之后，你会首先访问这个ready set。Selection将在下一小节进行解释。可以这样访问ready集合：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">int</span> readySet <span class="token operator">=</span> selectionKey<span class="token punctuation">.</span><span class="token function">readyOps</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>可以用像检测interest集合那样的方法，来检测channel中什么事件或操作已经就绪。但是，也可以使用以下四个方法，它们都会返回一个布尔类型：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java">selectionKey<span class="token punctuation">.</span><span class="token function">isAcceptable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
selectionKey<span class="token punctuation">.</span><span class="token function">isConnectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
selectionKey<span class="token punctuation">.</span><span class="token function">isReadable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
selectionKey<span class="token punctuation">.</span><span class="token function">isWritable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
</blockquote>
<blockquote>
<p><strong>Channel+Selector</strong></p>
<p>从SelectionKey访问Channel和Selector很简单。如下：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Channel</span>  channel  <span class="token operator">=</span> selectionKey<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">Selector</span> selector <span class="token operator">=</span> selectionKey<span class="token punctuation">.</span><span class="token function">selector</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
</blockquote>
<blockquote>
<p><strong>附加对象</strong></p>
<p>可以将一个对象或者更多信息附着到SelectionKey上，这样就能方便的识别某个给定的通道。例如，可以附加 与通道一起使用的Buffer，或是包含聚集数据的某个对象。使用方法如下：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java">selectionKey<span class="token punctuation">.</span><span class="token function">attach</span><span class="token punctuation">(</span>theObject<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">Object</span> attachedObj <span class="token operator">=</span> selectionKey<span class="token punctuation">.</span><span class="token function">attachment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<p>还可以在用<code>register()</code>方法向Selector注册Channel的时候附加对象。如：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">SelectionKey</span> key <span class="token operator">=</span> channel<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_READ<span class="token punctuation">,</span> theObject<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</blockquote>
<h3 id="监听Channel事件"><a href="#监听Channel事件" class="headerlink" title="监听Channel事件"></a>监听Channel事件</h3><p>一旦向Selector注册了一个或多个通道，就可用调用<code>select()</code>方法，这些方法会根据你所感兴趣的事件（如连接、接受、读或写）已经准备就绪的那些通道。</p>
<p>方法1，阻塞直到绑定事件发生</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">int</span> count <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>



<p>方法2，阻塞直到绑定事件发生，或是超时（时间单位为 ms）</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">int</span> count <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token keyword">long</span> timeout<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>



<p>方法3，不会阻塞，也就是不管有没有事件，立刻返回，自己根据返回值检查是否有事件</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">int</span> count <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">selectNow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>



<h4 id="select-何时不阻塞"><a href="#select-何时不阻塞" class="headerlink" title="select 何时不阻塞"></a>select 何时不阻塞</h4><blockquote>
<ul>
<li>事件发生时<ul>
<li>客户端发起连接请求，会触发 accept 事件</li>
<li>客户端发送数据过来，客户端正常、异常关闭时，都会触发 read 事件，另外如果发送的数据大于 buffer 缓冲区，会触发多次读取事件</li>
<li>channel 可写，会触发 write 事件</li>
<li>在 linux 下 nio bug 发生时</li>
</ul>
</li>
<li>调用 selector.wakeup()</li>
<li>调用 selector.close()</li>
<li>selector 所在线程 interrupt</li>
</ul>
</blockquote>
<h3 id="selectedKeys"><a href="#selectedKeys" class="headerlink" title="selectedKeys()"></a>selectedKeys()</h3><p>一旦<code>select()</code>方法执行完毕，就可以通过调用selector的<code>selectedKeys()</code>方法，访问就绪通道，如下：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> selectionKeys <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 或</span>
<span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> iter <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<p>可以通过遍历这个集合访问就绪的通道，如下：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> iter <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span>iter<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token class-name">SelectionKey</span> key <span class="token operator">=</span> iter<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isAcceptable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token comment">// a connection was accepted by a ServerSocketChannel.</span>
    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isConnectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token comment">// a connection was established with a remote server.</span>
    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isReadable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token comment">// a channel is ready for reading</span>
    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isWritable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token comment">// a channel is ready for writing</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>这个循环遍历了所有已就绪的Key，并检测对应的事件。</p>
<p>SelectionKey.channel()方法返回的通道需要转型成你要处理的类型，如ServerSocketChannel或SocketChannel等。</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">ServerSocketChannel</span> channel <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">ServerSocketChannel</span><span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>完整的示例：</p>
<p>server端：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">//1.创建selector对象 管理多个channel</span>
<span class="token class-name">Selector</span> selector <span class="token operator">=</span> <span class="token class-name">Selector</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token class-name">ServerSocketChannel</span> ssc <span class="token operator">=</span> <span class="token class-name">ServerSocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ssc<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ssc<span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token number">8080</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">//2.建立selector和channel的联系/注册</span>
<span class="token comment">//SelectionKey 就是将来事件发生后 通过它可以知道事件和哪个channel的事件</span>
<span class="token class-name">SelectionKey</span> sscKey <span class="token operator">=</span> ssc<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//key 只关注 accpet 事件</span>
sscKey<span class="token punctuation">.</span><span class="token function">interestOps</span><span class="token punctuation">(</span><span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_ACCEPT<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"register: "</span> <span class="token operator">+</span> sscKey<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token comment">//3. select方法</span>
    <span class="token comment">//select在事件未处理时，它不会阻塞，事件发生后要么处理，要么取消，不能不管</span>
    selector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//阻塞方法 没有事件发生时会阻塞 发生事件继续运行</span>

    <span class="token comment">//4. 处理事件 selectedKeys()内部包含了所有发生的事件</span>
    <span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> iter <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span>iter<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token class-name">SelectionKey</span> key <span class="token operator">=</span> iter<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"key: "</span> <span class="token operator">+</span> key<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">//处理事件</span>
        <span class="token class-name">ServerSocketChannel</span> channel <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">ServerSocketChannel</span><span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">SocketChannel</span> accept <span class="token operator">=</span> channel<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">""</span> <span class="token operator">+</span> accept<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">//取消事件</span>
        <span class="token comment">//key.channel();</span>
        <span class="token comment">// 处理完毕，必须将事件移除</span>
        iter<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<blockquote>
<p>事件发生后，要么处理，要么取消（cancel），不能什么都不做，否则下次该事件仍会触发，这是因为 nio 底层使用的是水平触发</p>
</blockquote>
<blockquote>
<p>因为 select 在事件发生后，就会将相关的 key 放入 selectedKeys 集合，但不会在处理完后从 selectedKeys 集合中移除，需要我们自己编码删除。例如</p>
<ul>
<li>第一次触发了 ssckey 上的 accept 事件，没有移除 ssckey </li>
<li>第二次触发了 sckey 上的 read 事件，但这时 selectedKeys 中还有上次的 ssckey ，在处理时因为没有真正的 serverSocket 连上了，就会导致空指针异常</li>
</ul>
</blockquote>
<p>client端：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">SocketChannel</span> sc <span class="token operator">=</span> <span class="token class-name">SocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
sc<span class="token punctuation">.</span><span class="token function">connect</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token string">"localhost"</span><span class="token punctuation">,</span><span class="token number">8080</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"waiting..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<h2 id="消息边界问题"><a href="#消息边界问题" class="headerlink" title="消息边界问题"></a>消息边界问题</h2><p>在服务器端接收到的消息，长度是不定的，如图：</p>
<p><img src="/2021/09/28/Java-NIO/image-20211107221846884.png" alt="image-20211107221846884" srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="/2021/09/28/Java-NIO/image-20211107221846884.png" class="lozad post-image"></p>
<p>为了解决这种情况，有如下三种方法：</p>
<ol>
<li>固定消息长度，数据大小一样，服务器按照预定长度读取</li>
<li>分隔符拆分</li>
<li>TLV格式(T：Type类型，L：Length长度，V：Value类型)，类型和格式已知的情况下，去分配Buffer<ul>
<li>HTTP1是TLV格式</li>
<li>HTTP2是LTV格式</li>
</ul>
</li>
</ol>
<p><img src="/2021/09/28/Java-NIO/image-20211114145030707.png" alt="image-20211114145030707" srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="/2021/09/28/Java-NIO/image-20211114145030707.png" class="lozad post-image"></p>
<p>使用第二种方式的代码：</p>
<p>服务器端</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span> <span class="token punctuation">&#123;</span>
    <span class="token comment">//1.创建selector对象 管理多个channel</span>
    <span class="token class-name">Selector</span> selector <span class="token operator">=</span> <span class="token class-name">Selector</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token class-name">ServerSocketChannel</span> ssc <span class="token operator">=</span> <span class="token class-name">ServerSocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    ssc<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    ssc<span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token number">8080</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//2.建立selector和channel的联系/注册</span>
    <span class="token comment">//SelectionKey 就是将来事件发生后 通过它可以知道事件和哪个channel的事件</span>
    <span class="token class-name">SelectionKey</span> sscKey <span class="token operator">=</span> ssc<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//key 只关注 accpet 事件</span>
    sscKey<span class="token punctuation">.</span><span class="token function">interestOps</span><span class="token punctuation">(</span><span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_ACCEPT<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"register: "</span> <span class="token operator">+</span> sscKey<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token comment">//3. select方法</span>
        <span class="token comment">//select在事件未处理时，它不会阻塞，事件发生后要么处理，要么取消，不能不管</span>
        selector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//阻塞方法 没有事件发生时会阻塞 发生事件继续运行</span>

        <span class="token comment">//4. 处理事件 selectedKeys()内部包含了所有发生的事件</span>
        <span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> iter <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">while</span> <span class="token punctuation">(</span>iter<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
            <span class="token class-name">SelectionKey</span> key <span class="token operator">=</span> iter<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            iter<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//处理key后 必须从selectedKeys()中删除 否则下次还会处理</span>
            <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"key: "</span> <span class="token operator">+</span> key<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">//5.区分事件类型</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isAcceptable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span> <span class="token comment">//如果是accpet</span>
                <span class="token class-name">ServerSocketChannel</span> channel <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">ServerSocketChannel</span><span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token class-name">SocketChannel</span> sc <span class="token operator">=</span> channel<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                sc<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token class-name">ByteBuffer</span> buffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">//将一个ByteBuffer作为附件关联到selectionKey上</span>
                <span class="token class-name">SelectionKey</span> scKey <span class="token operator">=</span> sc<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> buffer<span class="token punctuation">)</span><span class="token punctuation">;</span>
                scKey<span class="token punctuation">.</span><span class="token function">interestOps</span><span class="token punctuation">(</span><span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_READ<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">""</span> <span class="token operator">+</span> sc<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isReadable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token comment">//如果是read</span>
                <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>
                    <span class="token class-name">SocketChannel</span> channel <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">SocketChannel</span><span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                    <span class="token comment">//                        ByteBuffer buffer = ByteBuffer.allocate(16);</span>
                    <span class="token class-name">ByteBuffer</span> buffer <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">ByteBuffer</span><span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">attachment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//从附件中获取</span>
                    <span class="token keyword">int</span> read <span class="token operator">=</span> channel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span><span class="token punctuation">;</span>
                    <span class="token keyword">if</span> <span class="token punctuation">(</span>read <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token comment">//-1表示客户端主动断开连接</span>
                        key<span class="token punctuation">.</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"一个客户端断开连接"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>
                        <span class="token function">split</span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span><span class="token punctuation">;</span>
                        <span class="token keyword">if</span> <span class="token punctuation">(</span>buffer<span class="token punctuation">.</span><span class="token function">position</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> buffer<span class="token punctuation">.</span><span class="token function">limit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span> <span class="token comment">//如果读到的消息没用结束符 则进行扩容</span>
                            <span class="token class-name">ByteBuffer</span> newBuffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span>buffer<span class="token punctuation">.</span><span class="token function">capacity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                            buffer<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//切换到读模式</span>
                            newBuffer<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span><span class="token punctuation">;</span>
                            key<span class="token punctuation">.</span><span class="token function">attach</span><span class="token punctuation">(</span>newBuffer<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//将新的Buffer放到附件中</span>
                        <span class="token punctuation">&#125;</span>
                    <span class="token punctuation">&#125;</span>
                <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
                    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                    <span class="token comment">//因为客户端断开了，因此需要将key取消(从selector的keys中真正删除key)</span>
                    key<span class="token punctuation">.</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">&#125;</span>
            <span class="token punctuation">&#125;</span>
        <span class="token punctuation">&#125;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">split</span><span class="token punctuation">(</span><span class="token class-name">ByteBuffer</span> source<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    source<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> source<span class="token punctuation">.</span><span class="token function">limit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token comment">//找到一条完整消息</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>source<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">'\n'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
            <span class="token keyword">int</span> length <span class="token operator">=</span> i <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">-</span> source<span class="token punctuation">.</span><span class="token function">position</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">//将完整消息存入新的Buffer</span>
            <span class="token class-name">ByteBuffer</span> target <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">//从 source 读 ， 向 target 写</span>
            <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> length<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
                target<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>source<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">&#125;</span>
            <span class="token class-name">ByteBufferUtil</span><span class="token punctuation">.</span><span class="token function">debugAll</span><span class="token punctuation">(</span>target<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">&#125;</span>
    <span class="token punctuation">&#125;</span>
    source<span class="token punctuation">.</span><span class="token function">compact</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>客户端</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">SocketChannel</span> sc <span class="token operator">=</span> <span class="token class-name">SocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
sc<span class="token punctuation">.</span><span class="token function">connect</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token string">"localhost"</span><span class="token punctuation">,</span><span class="token number">8080</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
sc<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token class-name">Charset</span><span class="token punctuation">.</span><span class="token function">defaultCharset</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span><span class="token string">"1234567890\n0987654321aaaa"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
sc<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token class-name">Charset</span><span class="token punctuation">.</span><span class="token function">defaultCharset</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span><span class="token string">"12345678900987654321\n"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"waiting..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>in<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="NIO-vs-BIO"><a href="#NIO-vs-BIO" class="headerlink" title="NIO vs BIO"></a>NIO vs BIO</h2><h3 id="Stream-vs-Channel"><a href="#Stream-vs-Channel" class="headerlink" title="Stream vs Channel"></a>Stream vs Channel</h3><ul>
<li>Stream不会自动缓存数据，channel会利用系统提供的发送缓冲区、接收缓冲区</li>
<li>Stream仅支持阻塞API，Channel同时支持阻塞、非阻塞API，网络Channel可配合Selector实现多路复用</li>
<li>二者均为全双工，即读写可以同时进行</li>
</ul>
<h3 id="IO模型"><a href="#IO模型" class="headerlink" title="IO模型"></a>IO模型</h3><p>同步阻塞、同步非阻塞、同步多路复用、异步阻塞(不存在的)、异步非阻塞</p>
<ul>
<li>同步：线程自己去获取结果(一个线程)</li>
<li>异步：线程不自己获取结果，而是由其他线程发送结果(至少两个线程)</li>
</ul>
<h3 id="零拷贝"><a href="#零拷贝" class="headerlink" title="零拷贝"></a>零拷贝</h3><h4 id="传统IO问题"><a href="#传统IO问题" class="headerlink" title="传统IO问题"></a>传统IO问题</h4><p>传统的IO将一个文件通过Socket写出</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">File</span> f <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">File</span><span class="token punctuation">(</span><span class="token string">"helloword/data.txt"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">RandomAccessFile</span> file <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RandomAccessFile</span><span class="token punctuation">(</span>file<span class="token punctuation">,</span> <span class="token string">"r"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> buf <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>f<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
file<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>buf<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token class-name">Socket</span> socket <span class="token operator">=</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">;</span>
socket<span class="token punctuation">.</span><span class="token function">getOutputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>buf<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>内部工作流程：</p>
<p><img src="/2021/09/28/Java-NIO/image-20211204111314653.png" alt="image-20211204111314653" srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="/2021/09/28/Java-NIO/image-20211204111314653.png" class="lozad post-image"></p>
<ol>
<li><p>java本身不具备IO读写能力，因此Read方法调用后，要从java程序的<strong>用户态</strong>切换到<strong>内核态</strong>，去调用操作系统（Kernel）的读能力，将数据读入<strong>内核缓冲区</strong>。这期间用户线程阻塞，操作系统会使用DMA(Direct Memory Access)来实现文件读，期间也不会使用CPU</p>
<blockquote>
<p>DMA也可以理解为硬件单元，用来解放 cpu 完成文件 IO</p>
</blockquote>
</li>
<li><p>从<strong>内核态</strong>切换回<strong>用户态</strong>，将数据从<strong>内核缓冲区</strong>读入<strong>用户缓冲区</strong>（即byte[] buf），这期间cpu会参与拷贝，无法利用cpu</p>
</li>
<li><p>调用write方法，这时将数据从<strong>用户缓冲区</strong>（即byte[] buf）写入<strong>socket缓冲区</strong>，cpu会参与拷贝</p>
</li>
<li><p>接下来要向网卡写入数据，这项能力java又不具备，因此又得从<strong>用户态</strong>切换至<strong>内核态</strong>，调用操作系统的写能力，使用DMA将<strong>socket缓冲区</strong>的数据写入网卡，又不会使用cpu</p>
</li>
</ol>
<p>可以看到中间环节较多，java的IO实际不是物理设备级别的读写，而是缓存的复制，底层真正的读写是又操作系统来完成的</p>
<ul>
<li>用户态和内核态的切换发生了3次，这是重量级的</li>
<li>数据拷贝了共4次</li>
</ul>
<h4 id="NIO"><a href="#NIO" class="headerlink" title="NIO"></a>NIO</h4><p>通过<code>DirectByteBuff</code></p>
<ul>
<li><code>ByteBuffer.allocate(10)  HeapByteBuffer</code> <strong>使用的是 java 内存</strong></li>
<li><code>ByteBuffer.allocateDirect(10)  DirectByteBuffer</code> <strong>使用的是操作系统内存</strong></li>
</ul>
<p><img src="/2021/09/28/Java-NIO/image-20211204112340504.png" alt="image-20211204112340504" srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="/2021/09/28/Java-NIO/image-20211204112340504.png" class="lozad post-image"></p>
<p>基本和优化前相同，唯有一点：java可以通过 <code>DirectByteBuff </code> 将堆外内存映射到jvm内存中来直接访问使用</p>
<ul>
<li>这块内存不受jvm垃圾回收的影响，因此内存地址固定，有助于IO读写</li>
<li>java中的DirectByteBuf对象仅维护了此内存的虚引用，内存回收分成两部<ul>
<li>DirectByteBuf对象垃圾回收，将虚引用加入引用队列</li>
<li>通过专门线程访问引用队列，根据虚引用是否堆外内存</li>
</ul>
</li>
<li>减少了一次数据拷贝，用户态和内核态的切换次数没用减少</li>
</ul>
<hr>
<p>进一步优化（底层使用了lunux2.1后提供的sendFile方法），java中对应着两个channel调用<code>transferTo</code>和<code>transferForm</code>方法拷贝数据</p>
<p><img src="/2021/09/28/Java-NIO/image-20211204113142011.png" alt="image-20211204113142011" srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="/2021/09/28/Java-NIO/image-20211204113142011.png" class="lozad post-image"></p>
<ol>
<li>java调用transferTo方法后，要从java程序的<strong>用户态</strong>切换至<strong>内核态</strong>，使用DMA将数据读入<strong>内核缓冲区</strong>，不会使用</li>
<li>数据从<strong>内核缓冲区</strong>传输到socket缓冲区，cpu会参与拷贝</li>
<li>最后使用DMA将socket缓冲区的数据写入网卡，不会使用cpu</li>
</ol>
<p>可以看到，只发生了一次用户态和内核态的切换，数据拷贝了3次</p>
<hr>
<p>再进一步优化（linux2.4）</p>
<p><img src="/2021/09/28/Java-NIO/image-20211204113641189.png" alt="image-20211204113641189" srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="/2021/09/28/Java-NIO/image-20211204113641189.png" class="lozad post-image"></p>
<ol>
<li>java调用transferTo方法后，要从java程序的<strong>用户态</strong>切换到<strong>内核态</strong>，使用DMA将数据读入<strong>内核缓冲区</strong>，不会使用cpu</li>
<li>只会将一些 offset 和 length 信息考入 <strong>socket缓冲区</strong>，几乎无消耗</li>
<li>使用DMA将<strong>内核缓冲区</strong>的数据写入网卡，不会使用cpu</li>
</ol>
<p>整个过程同意只发生了一次<strong>用户态</strong>和<strong>内核态</strong>的切换，数据拷贝了2次，所谓的<strong>零拷贝</strong>，并不是真正的无拷贝，而是不会拷贝重复数据到jvm内存中，零拷贝的的优点：</p>
<ul>
<li>更少的用户态与内核态的切换</li>
<li>不利于cpu计算，减少cpu利用</li>
<li>零拷贝适合小文件传输</li>
</ul>
<h3 id="AIO"><a href="#AIO" class="headerlink" title="AIO"></a>AIO</h3><p>AIO用于解决数据复制阶段的阻塞问题</p>
<ul>
<li>同步意味着，在进行读写操作时，线程需要等待结果，相当于闲置</li>
<li>异步意味着，在进行读写操作时，线程不必等待结果，而是由操作系统来通过回调方式由另外的线程获得结果</li>
</ul>
<blockquote>
<p>异步模型需要底层操作系统(Kernel)提供支持</p>
<ul>
<li>Windows系统通过IOCP实现了真正的异步IO</li>
<li>Linux系统异步IO在2.6版本引入，但是底层实现还是多路复用模拟异步IO，性能没优势</li>
</ul>
</blockquote>
<p>案例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AioFileChannel</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span> <span class="token punctuation">&#123;</span>
        <span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">AsynchronousFileChannel</span> channel <span class="token operator">=</span> <span class="token class-name">AsynchronousFileChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token class-name">Paths</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"data.txt"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">StandardOpenOption</span><span class="token punctuation">.</span>READ<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
            <span class="token comment">//参数1：ByterBuffer</span>
            <span class="token comment">//参数2：读取的起始位置</span>
            <span class="token comment">//参数3：附件</span>
            <span class="token comment">//参数4：回调对象</span>
            <span class="token class-name">ByteBuffer</span> buffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"read begin..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            channel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>buffer<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> buffer<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">CompletionHandler</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Integer</span><span class="token punctuation">,</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
                <span class="token annotation punctuation">@Override</span>
                <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">completed</span><span class="token punctuation">(</span><span class="token class-name">Integer</span> result<span class="token punctuation">,</span> <span class="token class-name">ByteBuffer</span> attachment<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
                    log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"read completed...&#123;&#125;"</span><span class="token punctuation">,</span> result<span class="token punctuation">)</span><span class="token punctuation">;</span>
                    attachment<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                    <span class="token function">debugAll</span><span class="token punctuation">(</span>attachment<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">&#125;</span>

                <span class="token annotation punctuation">@Override</span>
                <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">failed</span><span class="token punctuation">(</span><span class="token class-name">Throwable</span> exc<span class="token punctuation">,</span> <span class="token class-name">ByteBuffer</span> attachment<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
                    log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>exc<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">&#125;</span>
            <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"read end..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
            e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">&#125;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>in<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>输出</p>
<pre class="line-numbers language-none"><code class="language-none">13:44:56 [DEBUG] [main] c.i.aio.AioDemo1 - begin...
13:44:56 [DEBUG] [main] c.i.aio.AioDemo1 - do other things...
13:44:56 [DEBUG] [Thread-5] c.i.aio.AioDemo1 - read completed...2
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 0d                                           |a.              |
+--------+-------------------------------------------------+----------------+<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<ul>
<li>相应文件读取成功的是另一个线程Thread-5</li>
<li>主线程并没用IO操作</li>
</ul>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p>文章1: <a target="_blank" rel="noopener" href="https://blog.csdn.net/forezp/article/details/88414741/">https://blog.csdn.net/forezp/article/details/88414741/</a></p>
<p>文章2: <a target="_blank" rel="noopener" href="https://ifeve.com/java-nio-all/">https://ifeve.com/java-nio-all/</a></p>
<p>视频教程: <a target="_blank" rel="noopener" href="https://www.bilibili.com/video/BV1py4y1E7oA">https://www.bilibili.com/video/BV1py4y1E7oA</a></p>

  </div>
  <div>
    
      <div 
        class="post-note note-warning copyright" 
        style="margin-top: 42px">
        <p>
          <span style="font-weight: bold;">作者：</span><a 
            target="_blank" 
            rel="nofollow noopener noreferrer" 
            href="/about">
            Gas
          </a>
        </p>
        <p>
          <span style="font-weight: bold;">文章链接：</span><a 
            target="_blank" 
            rel="nofollow noopener noreferrer" 
            href="http://example.com/2021/09/28/Java-NIO/">
            http://example.com/2021/09/28/Java-NIO/
          </a>
        </p>
        <p><span style="font-weight: bold;">版权声明：</span>本博客所有文章除特别声明外，均采用<a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh">CC BY-NC-SA 4.0 协议</a>。转载请注明出处！</p>
      </div>
    
  </div>
</article>
<div class="nav">
  
    <div class="nav-item-prev">
      <a 
        href="/2021/10/17/Jenkins%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2/" 
        class="nav-link">
        <i class="iconfont icon-left nav-prev-icon"></i>
        <div>
          <div class="nav-label">上一篇</div>
          
            <div class="nav-title">Jenkins自动部署 </div>
          
        </div>
      </a>
    </div>
  
  
    <div class="nav-item-next">
      <a 
        href="/2021/09/13/IText%E5%AE%9E%E7%8E%B0PDF%E8%87%AA%E5%8A%A8%E5%AE%9A%E4%BD%8D%E7%AD%BE%E5%90%88%E5%90%8C/" 
        class="nav-link">
        <div>
          <div class="nav-label">下一篇</div>
          
            <div class="nav-title">IText实现PDF自动定位签合同 </div>
          
        </div>
        <i class="iconfont icon-right nav-next-icon"></i>
      </a>
    </div>
  
</div>

<div 
  class="card card-content toc-card" 
  id="mobiletoc">
  <div class="toc-header">
  <i 
    class="iconfont icon-menu" 
    style="padding-right: 2px;">
  </i>目录
</div>
<ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Java-NIO-%E6%A6%82%E8%BF%B0"><span class="toc-number">1.</span> <span class="toc-text">Java NIO 概述</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Channel-%E5%92%8C-Buffer"><span class="toc-number">2.</span> <span class="toc-text">Channel 和 Buffer</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Selector"><span class="toc-number">3.</span> <span class="toc-text">Selector</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Buffer"><span class="toc-number">4.</span> <span class="toc-text">Buffer</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer%E7%9A%84%E7%B1%BB%E5%9E%8B"><span class="toc-number">4.1.</span> <span class="toc-text">Buffer的类型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer%E7%9A%84%E5%88%86%E9%85%8D"><span class="toc-number">4.2.</span> <span class="toc-text">Buffer的分配</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer%E5%86%99%E5%85%A5%E6%95%B0%E6%8D%AE"><span class="toc-number">4.3.</span> <span class="toc-text">Buffer写入数据</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer%E8%AF%BB%E5%8F%96%E6%95%B0%E6%8D%AE"><span class="toc-number">4.4.</span> <span class="toc-text">Buffer读取数据</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Scatter-Gather"><span class="toc-number">5.</span> <span class="toc-text">Scatter&#x2F;Gather</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Scatterering-Reads"><span class="toc-number">5.1.</span> <span class="toc-text">Scatterering Reads</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Gathering-Writes"><span class="toc-number">5.2.</span> <span class="toc-text">Gathering Writes</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%80%9A%E9%81%93%E4%B9%8B%E9%97%B4%E7%9A%84%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93"><span class="toc-number">6.</span> <span class="toc-text">通道之间的数据传输</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#transferTo"><span class="toc-number">6.1.</span> <span class="toc-text">transferTo()</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#transferFrom"><span class="toc-number">6.2.</span> <span class="toc-text">transferFrom()</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Selector-1"><span class="toc-number">7.</span> <span class="toc-text">Selector</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8Selector"><span class="toc-number">7.1.</span> <span class="toc-text">为什么使用Selector</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Selector%E7%9A%84%E5%88%9B%E5%BB%BA"><span class="toc-number">7.2.</span> <span class="toc-text">Selector的创建</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%BB%91%E5%AE%9AChannel%E4%BA%8B%E4%BB%B6"><span class="toc-number">7.3.</span> <span class="toc-text">绑定Channel事件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#SelectionKey"><span class="toc-number">7.4.</span> <span class="toc-text">SelectionKey</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%9B%91%E5%90%ACChannel%E4%BA%8B%E4%BB%B6"><span class="toc-number">7.5.</span> <span class="toc-text">监听Channel事件</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#select-%E4%BD%95%E6%97%B6%E4%B8%8D%E9%98%BB%E5%A1%9E"><span class="toc-number">7.5.1.</span> <span class="toc-text">select 何时不阻塞</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#selectedKeys"><span class="toc-number">7.6.</span> <span class="toc-text">selectedKeys()</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%B6%88%E6%81%AF%E8%BE%B9%E7%95%8C%E9%97%AE%E9%A2%98"><span class="toc-number">8.</span> <span class="toc-text">消息边界问题</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#NIO-vs-BIO"><span class="toc-number">9.</span> <span class="toc-text">NIO vs BIO</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Stream-vs-Channel"><span class="toc-number">9.1.</span> <span class="toc-text">Stream vs Channel</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#IO%E6%A8%A1%E5%9E%8B"><span class="toc-number">9.2.</span> <span class="toc-text">IO模型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%9B%B6%E6%8B%B7%E8%B4%9D"><span class="toc-number">9.3.</span> <span class="toc-text">零拷贝</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BC%A0%E7%BB%9FIO%E9%97%AE%E9%A2%98"><span class="toc-number">9.3.1.</span> <span class="toc-text">传统IO问题</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#NIO"><span class="toc-number">9.3.2.</span> <span class="toc-text">NIO</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#AIO"><span class="toc-number">9.4.</span> <span class="toc-text">AIO</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%82%E8%80%83"><span class="toc-number">10.</span> <span class="toc-text">参考</span></a></li></ol>
</div></main>
            <aside class="left-column">
              
              <div class="card card-author">
                
  <img 
    src="/img/Kaze.png" 
    class="author-img" 
    alt="author avatar">

<p class="author-name">Gas</p>
<p class="author-description">想当画家的咸鱼罢了</p>
<div class="author-message">
  <a 
    class="author-posts-count" 
    href="/archives">
    <span>51</span>
    <span>文章</span>
  </a>
  <a 
    class="author-categories-count" 
    href="/categories">
    <span>16</span>
    <span>分类</span>
  </a>
  <a 
    class="author-tags-count" 
    href="/tags">
    <span>44</span>
    <span>标签</span>
  </a>
</div>

  <div class="author-card-society">
    
      <div class="author-card-society-icon">
        <a target="_blank" rel="noopener" href="https://github.com/Uranus-s">
          <i class="iconfont social_github icon-github society-icon"></i>
        </a>
      </div>
    
      <div class="author-card-society-icon">
        <a target="_blank" rel="noopener" href="https://gitee.com/L-sama">
          <i class="iconfont social_github icon-gitee2 society-icon"></i>
        </a>
      </div>
    
  </div>

              </div>
               <div class="sticky-tablet">
  
  
    <article class="display-when-two-columns spacer">
      <div class="card card-content toc-card">
        <div class="toc-header">
  <i 
    class="iconfont icon-menu" 
    style="padding-right: 2px;">
  </i>目录
</div>
<ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Java-NIO-%E6%A6%82%E8%BF%B0"><span class="toc-number">1.</span> <span class="toc-text">Java NIO 概述</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Channel-%E5%92%8C-Buffer"><span class="toc-number">2.</span> <span class="toc-text">Channel 和 Buffer</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Selector"><span class="toc-number">3.</span> <span class="toc-text">Selector</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Buffer"><span class="toc-number">4.</span> <span class="toc-text">Buffer</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer%E7%9A%84%E7%B1%BB%E5%9E%8B"><span class="toc-number">4.1.</span> <span class="toc-text">Buffer的类型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer%E7%9A%84%E5%88%86%E9%85%8D"><span class="toc-number">4.2.</span> <span class="toc-text">Buffer的分配</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer%E5%86%99%E5%85%A5%E6%95%B0%E6%8D%AE"><span class="toc-number">4.3.</span> <span class="toc-text">Buffer写入数据</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer%E8%AF%BB%E5%8F%96%E6%95%B0%E6%8D%AE"><span class="toc-number">4.4.</span> <span class="toc-text">Buffer读取数据</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Scatter-Gather"><span class="toc-number">5.</span> <span class="toc-text">Scatter&#x2F;Gather</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Scatterering-Reads"><span class="toc-number">5.1.</span> <span class="toc-text">Scatterering Reads</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Gathering-Writes"><span class="toc-number">5.2.</span> <span class="toc-text">Gathering Writes</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%80%9A%E9%81%93%E4%B9%8B%E9%97%B4%E7%9A%84%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93"><span class="toc-number">6.</span> <span class="toc-text">通道之间的数据传输</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#transferTo"><span class="toc-number">6.1.</span> <span class="toc-text">transferTo()</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#transferFrom"><span class="toc-number">6.2.</span> <span class="toc-text">transferFrom()</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Selector-1"><span class="toc-number">7.</span> <span class="toc-text">Selector</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8Selector"><span class="toc-number">7.1.</span> <span class="toc-text">为什么使用Selector</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Selector%E7%9A%84%E5%88%9B%E5%BB%BA"><span class="toc-number">7.2.</span> <span class="toc-text">Selector的创建</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%BB%91%E5%AE%9AChannel%E4%BA%8B%E4%BB%B6"><span class="toc-number">7.3.</span> <span class="toc-text">绑定Channel事件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#SelectionKey"><span class="toc-number">7.4.</span> <span class="toc-text">SelectionKey</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%9B%91%E5%90%ACChannel%E4%BA%8B%E4%BB%B6"><span class="toc-number">7.5.</span> <span class="toc-text">监听Channel事件</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#select-%E4%BD%95%E6%97%B6%E4%B8%8D%E9%98%BB%E5%A1%9E"><span class="toc-number">7.5.1.</span> <span class="toc-text">select 何时不阻塞</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#selectedKeys"><span class="toc-number">7.6.</span> <span class="toc-text">selectedKeys()</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%B6%88%E6%81%AF%E8%BE%B9%E7%95%8C%E9%97%AE%E9%A2%98"><span class="toc-number">8.</span> <span class="toc-text">消息边界问题</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#NIO-vs-BIO"><span class="toc-number">9.</span> <span class="toc-text">NIO vs BIO</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Stream-vs-Channel"><span class="toc-number">9.1.</span> <span class="toc-text">Stream vs Channel</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#IO%E6%A8%A1%E5%9E%8B"><span class="toc-number">9.2.</span> <span class="toc-text">IO模型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%9B%B6%E6%8B%B7%E8%B4%9D"><span class="toc-number">9.3.</span> <span class="toc-text">零拷贝</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BC%A0%E7%BB%9FIO%E9%97%AE%E9%A2%98"><span class="toc-number">9.3.1.</span> <span class="toc-text">传统IO问题</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#NIO"><span class="toc-number">9.3.2.</span> <span class="toc-text">NIO</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#AIO"><span class="toc-number">9.4.</span> <span class="toc-text">AIO</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%82%E8%80%83"><span class="toc-number">10.</span> <span class="toc-text">参考</span></a></li></ol>
      </div>
    </article>
  
  
  <article class="card card-content categories-widget">
    <div class="categories-card">
  <div class="categories-header">
    <i 
      class="iconfont icon-fenlei" 
      style="padding-right: 2px;">
    </i>分类
  </div>
  <div class="categories-list">
    
      <a href="/categories/Java/">
        <div class="categories-list-item">
          Java
          <span class="categories-list-item-badge">11</span>
        </div>
      </a>
    
      <a href="/categories/SpringBoot/">
        <div class="categories-list-item">
          SpringBoot
          <span class="categories-list-item-badge">16</span>
        </div>
      </a>
    
      <a href="/categories/WPF/">
        <div class="categories-list-item">
          WPF
          <span class="categories-list-item-badge">1</span>
        </div>
      </a>
    
      <a href="/categories/Docker/">
        <div class="categories-list-item">
          Docker
          <span class="categories-list-item-badge">3</span>
        </div>
      </a>
    
      <a href="/categories/Net/">
        <div class="categories-list-item">
          Net
          <span class="categories-list-item-badge">2</span>
        </div>
      </a>
    
      <a href="/categories/DotNetCore/">
        <div class="categories-list-item">
          DotNetCore
          <span class="categories-list-item-badge">2</span>
        </div>
      </a>
    
      <a href="/categories/%E6%8E%92%E5%BA%8F/">
        <div class="categories-list-item">
          排序
          <span class="categories-list-item-badge">1</span>
        </div>
      </a>
    
      <a href="/categories/Redis/">
        <div class="categories-list-item">
          Redis
          <span class="categories-list-item-badge">6</span>
        </div>
      </a>
    
      <a href="/categories/COS/">
        <div class="categories-list-item">
          COS
          <span class="categories-list-item-badge">1</span>
        </div>
      </a>
    
      <a href="/categories/Spring/">
        <div class="categories-list-item">
          Spring
          <span class="categories-list-item-badge">1</span>
        </div>
      </a>
    
      <a href="/categories/IText/">
        <div class="categories-list-item">
          IText
          <span class="categories-list-item-badge">1</span>
        </div>
      </a>
    
      <a href="/categories/GateWay/">
        <div class="categories-list-item">
          GateWay
          <span class="categories-list-item-badge">1</span>
        </div>
      </a>
    
      <a href="/categories/Jenkins/">
        <div class="categories-list-item">
          Jenkins
          <span class="categories-list-item-badge">1</span>
        </div>
      </a>
    
      <a href="/categories/Netty/">
        <div class="categories-list-item">
          Netty
          <span class="categories-list-item-badge">1</span>
        </div>
      </a>
    
      <a href="/categories/Log4j/">
        <div class="categories-list-item">
          Log4j
          <span class="categories-list-item-badge">1</span>
        </div>
      </a>
    
      <a href="/categories/algorithm/">
        <div class="categories-list-item">
          algorithm
          <span class="categories-list-item-badge">1</span>
        </div>
      </a>
    
  </div>
</div>
  </article>
  
  <article class="card card-content tags-widget">
    <div class="tags-card">
  <div class="tags-header">
    <i 
      class="iconfont icon-biaoqian" 
      style="padding-right: 2px;">
    </i>热门标签
  </div>
  <div class="tags-list">
    
      <a 
        href="/tags/SpringBoot/" 
        title="SpringBoot">
        <div class="tags-list-item">SpringBoot</div>
      </a>
    
      <a 
        href="/tags/Spring/" 
        title="Spring">
        <div class="tags-list-item">Spring</div>
      </a>
    
      <a 
        href="/tags/Java/" 
        title="Java">
        <div class="tags-list-item">Java</div>
      </a>
    
      <a 
        href="/tags/Redis/" 
        title="Redis">
        <div class="tags-list-item">Redis</div>
      </a>
    
      <a 
        href="/tags/Docker/" 
        title="Docker">
        <div class="tags-list-item">Docker</div>
      </a>
    
      <a 
        href="/tags/DotNetCore/" 
        title="DotNetCore">
        <div class="tags-list-item">DotNetCore</div>
      </a>
    
      <a 
        href="/tags/IDEA/" 
        title="IDEA">
        <div class="tags-list-item">IDEA</div>
      </a>
    
      <a 
        href="/tags/Maven/" 
        title="Maven">
        <div class="tags-list-item">Maven</div>
      </a>
    
      <a 
        href="/tags/RabbitMQ/" 
        title="RabbitMQ">
        <div class="tags-list-item">RabbitMQ</div>
      </a>
    
      <a 
        href="/tags/Jackson/" 
        title="Jackson">
        <div class="tags-list-item">Jackson</div>
      </a>
    
      <a 
        href="/tags/snowflake/" 
        title="snowflake">
        <div class="tags-list-item">snowflake</div>
      </a>
    
      <a 
        href="/tags/algorithm/" 
        title="algorithm">
        <div class="tags-list-item">algorithm</div>
      </a>
    
      <a 
        href="/tags/Log4j/" 
        title="Log4j">
        <div class="tags-list-item">Log4j</div>
      </a>
    
      <a 
        href="/tags/Socket/" 
        title="Socket">
        <div class="tags-list-item">Socket</div>
      </a>
    
      <a 
        href="/tags/Netty/" 
        title="Netty">
        <div class="tags-list-item">Netty</div>
      </a>
    
      <a 
        href="/tags/Jenkins/" 
        title="Jenkins">
        <div class="tags-list-item">Jenkins</div>
      </a>
    
  </div>
</div>
  </article>
  
  
</div>
            </aside>
            <aside class="right-column">
              <div class="sticky-widescreen">
  
  
    <article class="card card-content toc-card">
      <div class="toc-header">
  <i 
    class="iconfont icon-menu" 
    style="padding-right: 2px;">
  </i>目录
</div>
<ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Java-NIO-%E6%A6%82%E8%BF%B0"><span class="toc-number">1.</span> <span class="toc-text">Java NIO 概述</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Channel-%E5%92%8C-Buffer"><span class="toc-number">2.</span> <span class="toc-text">Channel 和 Buffer</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Selector"><span class="toc-number">3.</span> <span class="toc-text">Selector</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Buffer"><span class="toc-number">4.</span> <span class="toc-text">Buffer</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer%E7%9A%84%E7%B1%BB%E5%9E%8B"><span class="toc-number">4.1.</span> <span class="toc-text">Buffer的类型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer%E7%9A%84%E5%88%86%E9%85%8D"><span class="toc-number">4.2.</span> <span class="toc-text">Buffer的分配</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer%E5%86%99%E5%85%A5%E6%95%B0%E6%8D%AE"><span class="toc-number">4.3.</span> <span class="toc-text">Buffer写入数据</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer%E8%AF%BB%E5%8F%96%E6%95%B0%E6%8D%AE"><span class="toc-number">4.4.</span> <span class="toc-text">Buffer读取数据</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Scatter-Gather"><span class="toc-number">5.</span> <span class="toc-text">Scatter&#x2F;Gather</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Scatterering-Reads"><span class="toc-number">5.1.</span> <span class="toc-text">Scatterering Reads</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Gathering-Writes"><span class="toc-number">5.2.</span> <span class="toc-text">Gathering Writes</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%80%9A%E9%81%93%E4%B9%8B%E9%97%B4%E7%9A%84%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93"><span class="toc-number">6.</span> <span class="toc-text">通道之间的数据传输</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#transferTo"><span class="toc-number">6.1.</span> <span class="toc-text">transferTo()</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#transferFrom"><span class="toc-number">6.2.</span> <span class="toc-text">transferFrom()</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Selector-1"><span class="toc-number">7.</span> <span class="toc-text">Selector</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8Selector"><span class="toc-number">7.1.</span> <span class="toc-text">为什么使用Selector</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Selector%E7%9A%84%E5%88%9B%E5%BB%BA"><span class="toc-number">7.2.</span> <span class="toc-text">Selector的创建</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%BB%91%E5%AE%9AChannel%E4%BA%8B%E4%BB%B6"><span class="toc-number">7.3.</span> <span class="toc-text">绑定Channel事件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#SelectionKey"><span class="toc-number">7.4.</span> <span class="toc-text">SelectionKey</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%9B%91%E5%90%ACChannel%E4%BA%8B%E4%BB%B6"><span class="toc-number">7.5.</span> <span class="toc-text">监听Channel事件</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#select-%E4%BD%95%E6%97%B6%E4%B8%8D%E9%98%BB%E5%A1%9E"><span class="toc-number">7.5.1.</span> <span class="toc-text">select 何时不阻塞</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#selectedKeys"><span class="toc-number">7.6.</span> <span class="toc-text">selectedKeys()</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%B6%88%E6%81%AF%E8%BE%B9%E7%95%8C%E9%97%AE%E9%A2%98"><span class="toc-number">8.</span> <span class="toc-text">消息边界问题</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#NIO-vs-BIO"><span class="toc-number">9.</span> <span class="toc-text">NIO vs BIO</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Stream-vs-Channel"><span class="toc-number">9.1.</span> <span class="toc-text">Stream vs Channel</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#IO%E6%A8%A1%E5%9E%8B"><span class="toc-number">9.2.</span> <span class="toc-text">IO模型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%9B%B6%E6%8B%B7%E8%B4%9D"><span class="toc-number">9.3.</span> <span class="toc-text">零拷贝</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BC%A0%E7%BB%9FIO%E9%97%AE%E9%A2%98"><span class="toc-number">9.3.1.</span> <span class="toc-text">传统IO问题</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#NIO"><span class="toc-number">9.3.2.</span> <span class="toc-text">NIO</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#AIO"><span class="toc-number">9.4.</span> <span class="toc-text">AIO</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%82%E8%80%83"><span class="toc-number">10.</span> <span class="toc-text">参考</span></a></li></ol>
    </article>
  
  
  <article class="card card-content">
    <div class="recent-posts-card">
  <div class="recent-posts-header">
    <i 
      class="iconfont icon-wenzhang_huaban" 
      style="padding-right: 2px;">
    </i>最近文章
  </div>
  <div class="recent-posts-list">
    
      <div class="recent-posts-item">
        <div class="recent-posts-item-title">2022-07-08</div>
        <a href="/2022/07/08/SpringBoot-RabbitMQ%E5%BA%94%E7%94%A8/"><div class="recent-posts-item-content">SpringBoot-RabbitMQ应用</div></a>
      </div>
    
      <div class="recent-posts-item">
        <div class="recent-posts-item-title">2022-03-26</div>
        <a href="/2022/03/26/IDEA%E9%83%A8%E7%BD%B2Docker%E9%A0%85%E7%9B%AE/"><div class="recent-posts-item-content">IDEA部署Docker項目</div></a>
      </div>
    
      <div class="recent-posts-item">
        <div class="recent-posts-item-title">2022-03-04</div>
        <a href="/2022/03/04/%E5%9F%BA%E4%BA%8EJackson%E7%9A%84JsonFilter%E5%AE%9E%E7%8E%B0%E5%8A%A8%E6%80%81%E6%95%B0%E6%8D%AE%E5%A1%AB%E5%86%99/"><div class="recent-posts-item-content">基于Jackson的JsonFilter实现动态数据填写</div></a>
      </div>
    
      <div class="recent-posts-item">
        <div class="recent-posts-item-title">2022-02-16</div>
        <a href="/2022/02/16/SpringBoot-%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3%E5%8D%95%E5%8F%82%E6%95%B0Post/"><div class="recent-posts-item-content">SpringBoot-自定义注解单参数Post</div></a>
      </div>
    
  </div>
</div>
  </article>
  
  
</div>
            </aside>
          </div>
        </div>
      </div>
    </div>
     
    <footer class="footer">
  <div class="footer-container">
    <div>
      <div class="footer-dsc">
        <span>
          Copyright ©
          
            2020 -
          
          2022
        </span>
        &nbsp;
        <a 
          href="/" 
          class="footer-link">
          Gas
        </a>
      </div>
    </div>

    
      <div class="footer-dsc">
        
          Powered by
          <a 
            href="https://hexo.io/" 
            class="footer-link" 
            target="_blank" 
            rel="nofollow noopener noreferrer">
            &nbsp;Hexo
          </a>
        
        
          <span>&nbsp;|&nbsp;</span>
        
        
          Theme -
          <a 
            href="https://github.com/theme-kaze" 
            class="footer-link" 
            target="_blank"
            rel="nofollow noopener noreferrer">
            &nbsp;Kaze
          </a>
        
      </div>
    
    
    
    
      <div class="footer-dsc">
        
          本站总访问量<span id="busuanzi_value_site_pv"></span>次
        
        
          <span>&nbsp;|&nbsp;</span>
        
        
          本站总访客数<span id="busuanzi_value_site_uv"></span>次
        
      </div>
      
    
</footer> 
    
  <a 
    role="button" 
    id="scrollbutton" 
    class="basebutton" 
    aria-label="回到顶部">
    <i class="iconfont icon-arrowleft button-icon"></i>
  </a>

<a 
  role="button" 
  id="menubutton" 
  class="basebutton">
  <i class="iconfont icon-menu button-icon"></i>
</a>
<a 
  role="button" 
  id="popbutton" 
  class="basebutton" 
  aria-label="控制中心">
  <i class="iconfont icon-expand button-icon"></i>
</a>
<a 
  role="button" 
  id="darkbutton" 
  class="basebutton darkwidget" 
  aria-label="夜色模式">
  <i class="iconfont icon-weather button-icon"></i>
</a>
<a 
  role="button" 
  id="searchbutton" 
  class="basebutton searchwidget" 
  aria-label="搜索">
  <i class="iconfont icon-search button-icon"></i>
</a> 
     
     
     
      <script>
  var addImgLayout = function () {
    var img = document.querySelectorAll('.post-content img')
    var i
    for (i = 0; i < img.length; i++) {
      var wrapper = document.createElement('a')
      wrapper.setAttribute('href', img[i].getAttribute('data-src'))
      wrapper.setAttribute('aria-label', 'illustration')
      wrapper.style.cssText =
        'width: 100%; display: flex; justify-content: center;'
      if (img[i].alt) wrapper.dataset.caption = img[i].alt
      wrapper.dataset.nolink = true
      img[i].before(wrapper)
      wrapper.append(img[i])
      var divWrap = document.createElement('div')
      divWrap.classList.add('gallery')
      wrapper.before(divWrap)
      divWrap.append(wrapper)
    }
    baguetteBox.run('.gallery')
  }
</script>
<script>
  loadScript(
    "/js/lib/lightbox/baguetteBox.min.js",
    addImgLayout
  )
</script>
 
     
     
    <script src="/js/main.js"></script> 
    
      <script> 
        loadScript('/js/lib/busuanzi.min.js') 
      </script>
     
    
      <script>
        var addLazyload = function () {
          var observer = lozad('.lozad', {
            load: function (el) {
              el.srcset = el.getAttribute('data-src')
            },
            loaded: function (el) {
              el.classList.add('loaded')
            },
          })
          observer.observe()
        }
      </script>
      <script>
        loadScript('/js/lib/lozad.min.js', addLazyload)
      </script>
     
    
    
      <script>
        setTimeout(() => {localSearch("search.json")}, 0)
      </script>
    
  </body>
</html>
